package world;

import java.util.Random;
import java.util.Vector;

import entity.Player;

public class ChunkManager {
/*
 * chunk list
 * list of all chunks
 * 
 * load list
 * list of chunks which potentially can be loaded
 * 
 * unload list
 * list of chunks which potentially need to be unloaded
 * 
 * chunk setup list
 * chunks which need to have terrain generated
 * 
 * chunk visible list
 * list of chunks which are within 100m of the player
 * 
 * chunk render list
 * list of chunks which the player can see.
 * 
 * 
 * 
 */
	private Vector<Chunk>  chunkList = new Vector<Chunk>();  
	
	private Vector<Integer> setupList = new Vector<Integer>();
	
	private Vector<Integer> loadList = new Vector<Integer>();
	
	private Vector<Integer> unloadList= new Vector<Integer>();
	
	private Vector<Integer> visibleList = new Vector<Integer>();
	
	private Vector<Integer> renderList = new Vector<Integer>();
	//private static int chunksPerSlice=10;
	private static int maxLoadChunks= 100*16;//the cap of chunks that can be loaded
	private static int loadedChunks =0;//the number of chunks currently loaded
	private static int chunksLoadPerFrame = 10;//the number of chunks that can be loaded or unloaded at one call
	private static boolean update =false;//flag for if there is an update
	private boolean fixedWorld=true;// a flag to control if new chunks will be generated
	private int[] worldSize={1,1,1};
	//1x1x1 chunks
	
	private double[] noiseModifer={0.05,1.5};
	private String seed="0101110101";
	
	public int[] getWorldSize() {
		return worldSize;
	}

	public void setWorldSize(int[] worldSize) {
		this.worldSize = worldSize;
	}

	public ChunkManager()
	{
		
	}
	
	public ChunkManager(boolean fixedWorld)
	{
		setFixedWorld(fixedWorld);
	}
	
	public void setFixedWorld(boolean fixedWorld)
	{
		this.fixedWorld=fixedWorld;
	}
	
	public void initialWorldGeneration()
	{
		
		
		try {
			setUpChunks();
			
			
			
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		//create enough chunks to fill the world and define each ones position in worldspace.
		
		
		
		
		
		
		//
		
	}//end initialWorldGeneration
	
	public Chunk getChunk(int x, int y, int z)
	{
		Chunk out=null;
	for(int i=0;i<chunkList.size();i++)
	{
		if(chunkList.elementAt(i).getXyz()[0]==x &&chunkList.elementAt(i).getXyz()[0]==y &&chunkList.elementAt(i).getXyz()[0]==z)
		{
			out = chunkList.elementAt(i);
		}
	}
	return out;	
	}//end getChunk
	
	public int[] getXYZFromChunk(Chunk chunk)
	{
		return chunk.getXyz();
	}
	public void manageChunks(Player player)
	{
		//called once per frame. Manages chunks
		loadListUpdate();
		//System.err.println("Existing blocks: "+chunkList.lastElement().getNumberOfExistingBlocks());
		
		updateSetupList();
		//System.err.println("Existing blocks: "+chunkList.lastElement().getNumberOfExistingBlocks());
		
		//unloadListUpdate();
		
		
		
		visibleListUpdate(player);//determine what chunks are within visible range of the player and are visible
		//System.err.println("Existing blocks: "+chunkList.lastElement().getNumberOfExistingBlocks());
		
		updateRenderList();//determine what the player can see
		//System.err.println("Existing blocks: "+chunkList.lastElement().getNumberOfExistingBlocks());
		
	}
	
	public void loadListUpdate()
	{
		
		int chunksLoaded=0;
		int numberOfChunksToLoad = this.loadList.size();
		
		for(int cIndex=0;cIndex<numberOfChunksToLoad;cIndex++){
			
			if(!chunkList.elementAt(loadList.elementAt(cIndex)).isLoaded())
			{
				
				if( chunksLoaded< chunksLoadPerFrame&&loadedChunks<maxLoadChunks)
				{
					
					//System.err.println("a chunk is loaded");
					chunkList.elementAt(loadList.elementAt(cIndex)).setLoaded(true);
					//set chunk to loaded
					setupList.add(loadList.elementAt(cIndex));//add the chunk to the setup list
					//add chunk to upload 
					
					//remove element
					
					//update = true;//flag to update is set
					
					//chunkList.indexOf(loadList.elementAt(cIndex));
					
					
					loadedChunks++;
					chunksLoaded++;
					
				}
				
			}
			else{
				//if the chunk is already loaded pass it to the setup list
				
				setupList.add(loadList.elementAt(cIndex));//add the chunk to the setup list
				
			}
			
			
		}//end for
		
		loadList.removeAllElements();
	}//end of load chunks
	
	public void unloadListUpdate()
	{
		int chunksUnLoaded=0;
		int numberOfChunksToUnLoad = this.unloadList.size();
		
		for(int cIndex=0;cIndex<numberOfChunksToUnLoad;cIndex++){
			
			if(chunkList.elementAt(unloadList.elementAt(cIndex)).isLoaded())
			{
				
				if( chunksUnLoaded< chunksLoadPerFrame)
				{
					
					
					chunkList.elementAt(chunkList.indexOf(unloadList.elementAt(cIndex))).setLoaded(false);
					//set chunk to loaded
					
					//save unloaded chunk
					
					
					
					
					update = true;//flag to update is set
					
					//chunkList.indexOf(loadList.elementAt(cIndex));
					
					
					
					chunksUnLoaded++;
					
				}
				
				
			}
			
			
		}//end for
		
		unloadList.removeAllElements();
	}
	
	public void updateSetupList()
	{
		int numberOfChunksToUpdate = setupList.size();
		
		for(int cIndex=0;cIndex<numberOfChunksToUpdate;cIndex++){
			
			if(!chunkList.elementAt(setupList.elementAt(cIndex)).isSetup())
			{
				
				//do chunk generation
				chunkList.elementAt(setupList.elementAt(cIndex)).setUpChunk();
				
				chunkList.elementAt(setupList.elementAt(cIndex)).setSetup(true);
				//add to 
				
			}
			
			//System.err.println("Chunk setup done");
				//add to visibility list
				visibleList.add(setupList.elementAt(cIndex));
			
			
			
		}//end of for
		
		setupList.removeAllElements();
	}
	
	public void updateRenderList()
	{
		int numberOfChunksToCheckForRender = renderList.size();
		
		//System.out.println("render list size :"+renderList.size());
		
		for(int cIndex=0;cIndex<numberOfChunksToCheckForRender;cIndex++){
		
				
				System.out.println("chunk set to be rendered ");
				chunkList.elementAt(renderList.elementAt(cIndex)).setWaitingToBeRendered(true);
			
			
		}
		
		renderList.removeAllElements();
		
		
		
		
	}//end
	
	public void visibleListUpdate(Player player)
	{
		int numberOfChunksToCheckForVisibility = visibleList.size();
		//System.out.println("num of chunks to check for visibility: "+numberOfChunksToCheckForVisibility);
		//int numOfVisibleChunks=0;	
		for(int cIndex=0;cIndex<numberOfChunksToCheckForVisibility;cIndex++){
			/*	
			if(chunkList.elementAt(visibleList.elementAt(cIndex)).getXyz()[0]<player.getPosX()-200)
			{
				chunkList.elementAt(visibleList.elementAt(cIndex)).setVisible(false);
			}else if(!chunkList.elementAt(visibleList.elementAt(cIndex)).isVisible())
			{
				chunkList.elementAt(visibleList.elementAt(cIndex)).setVisible(true);
			}
			
			
			if(chunkList.elementAt(visibleList.elementAt(cIndex)).getXyz()[0]>player.getPosX()+200)
			{
				chunkList.elementAt(visibleList.elementAt(cIndex)).setVisible(false);
			}else if(!chunkList.elementAt(visibleList.elementAt(cIndex)).isVisible())
			{
				chunkList.elementAt(visibleList.elementAt(cIndex)).setVisible(true);
			}
			
			if(chunkList.elementAt(visibleList.elementAt(cIndex)).getXyz()[1]<player.getPosY()-200)
			{
				chunkList.elementAt(visibleList.elementAt(cIndex)).setVisible(false);
			}else if(!chunkList.elementAt(visibleList.elementAt(cIndex)).isVisible())
			{
				chunkList.elementAt(visibleList.elementAt(cIndex)).setVisible(true);
			}
			
			if(chunkList.elementAt(visibleList.elementAt(cIndex)).getXyz()[1]>player.getPosY()+200)
			{
				chunkList.elementAt(visibleList.elementAt(cIndex)).setVisible(false);
			}else if(!chunkList.elementAt(visibleList.elementAt(cIndex)).isVisible())
			{
				chunkList.elementAt(visibleList.elementAt(cIndex)).setVisible(true);
			}
			
			if(chunkList.elementAt(visibleList.elementAt(cIndex)).getXyz()[2]<player.getPosZ()-200)
			{
				chunkList.elementAt(visibleList.elementAt(cIndex)).setVisible(false);
				
			}else if(!chunkList.elementAt(visibleList.elementAt(cIndex)).isVisible())
			{
				chunkList.elementAt(visibleList.elementAt(cIndex)).setVisible(true);
			}
			
			if(chunkList.elementAt(visibleList.elementAt(cIndex)).getXyz()[2]>player.getPosZ()+200)
			{
				chunkList.elementAt(visibleList.elementAt(cIndex)).setVisible(false);
			}else if(!chunkList.elementAt(visibleList.elementAt(cIndex)).isVisible())*/
			
				chunkList.elementAt(visibleList.elementAt(cIndex)).setVisible(true);
			
				if(chunkList.elementAt(visibleList.elementAt(cIndex)).isLoaded()&&!chunkList.elementAt(visibleList.elementAt(cIndex)).isRendered())
				{
				if(!emptyChunkCheck(chunkList.elementAt(visibleList.elementAt(cIndex))))
				{
					if(chunkList.elementAt(visibleList.elementAt(cIndex)).isVisible()){
						System.err.println("Chunk is visible and added to the render list");
					renderList.add(visibleList.elementAt(cIndex));
					}
					else{
						//System.err.println("Chunk is not visible");
						
					}
				
				}//if the chunk is empty dont add it to the list of renderable chunk
				}
				else
				{
					loadList.add(visibleList.elementAt(cIndex));
				}
			}
		visibleList.removeAllElements();
		//System.err.println("num of chunks visibility: "+numOfVisibleChunks);
		
	}// end visbile list update
	
	
	
	
	public boolean emptyChunkCheck(Chunk chunk)
	{
		
		//and empty chunk has no existing blocks
		
		
		int exists=0;
		if(chunk!=null)
		{
		Block[][][] blocks =chunk.blocks;
		
		for(int i1=0; i1< blocks.length;i1++)
		{
			
			for(int i2=0; i2< blocks.length;i2++)
			{
				
				for(int i3=0; i3< blocks.length;i3++)
				{
					
					if(blocks[i1][i2][i3].exists())
					{
						exists++;
					}//if the block exists
					
				}	
				
			}
		
		}
		
		}
		if(exists==0)
		{
			return true;
		}
		else
		{
			return false;
		}
	}
	public void setUpChunks()
	{
		
		for(int i1=0;i1<worldSize[0];i1++)
		{
			for(int i2=0;i2<worldSize[1];i2++)
			{
				for(int i3=0; i3<worldSize[2];i3++)
				{
					
					try {
						
					Random random= new Random(seed.hashCode());
					//chunkList.add(ChunkConfiguration.dirtChunk(i1,i2,i3));//define a new chunk at position xyz
					
					//System.err.println("Chunk" + "X:"+i1+ "Y:"+i2
						//	+ "Z:"+i3);\
					//System.err.println("X:"+chunkList.lastElement().getXyz()[0]+ "Y:"+chunkList.lastElement().getXyz()[1]
						//	+ "Z:"+chunkList.lastElement().getXyz()[2]);
					
					if(i2==worldSize[1]-1)
					{
						chunkList.add(ChunkConfiguration.grassAndDirtChunk(i1,i2,i3));
						//System.err.println(" i1: "+i1+" i2: "+i2+" i3: "+i3);
						
						
						double xVarience=noiseModifer[0]*random.nextDouble();
						double zVarience=noiseModifer[1]*random.nextDouble();
						double yVarience=0.1*random.nextDouble();
						// noise
						
						Chunk temp=chunkList.lastElement();
						
						temp=ChunkConfiguration.applySimplexNoise(i1,i2,i3,temp,xVarience,zVarience);
						//Chunk
						//temp=ChunkConfiguration.applyPerlinNoise(i1,i2,i3,temp,xVarience,zVarience);
						
						///temp=ChunkConfiguration.applyWorleyNoiseCuts(i1,i2,i3,temp);
						//temp=ChunkConfiguration.applyWorleyNoise(i1,i2,i3,temp,xVarience,yVarience,zVarience);
						
						chunkList.remove(chunkList.lastElement());
						
						chunkList.add(temp);
						//System.err.println("Existing blocks: "+chunkList.lastElement().getNumberOfExistingBlocks());
						
						//add the chunk to the load list
						//assuming y is the vertical axess place grass and dirt chunks on top.
					}
					else if(i2==worldSize[1]-2)
					{
						chunkList.add(ChunkConfiguration.dirtChunk(i1,i2,i3));//define a new chunk at position xyz
						
						Chunk temp = ChunkConfiguration.applyExistanceToWholeChunk(chunkList.lastElement());
						
						chunkList.remove(chunkList.lastElement());
						
						chunkList.add(temp);
						//System.err.println("worldSize i2: "+i2);
					}
					else
					{
						//chunkList.add(ChunkConfiguration.dirtChunk(i1,i2,i3));//define a new chunk at position xyz
						chunkList.add(ChunkConfiguration.stoneChunk(i1,i2,i3));
						Chunk temp = ChunkConfiguration.applyExistanceToWholeChunk(chunkList.lastElement());
						
						chunkList.remove(chunkList.lastElement());
						
						chunkList.add(temp);
					}
					
					loadList.add(chunkList.indexOf(chunkList.lastElement()));
					
					
					} catch (Exception e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
						System.err.println("Error in setUpChunks");
					}
				
				}
				
				
			}
			
			
		}
		
		 
	}// end of setUpChunk
	/**
	 * @return the renderList
	 */
	
	public Vector<Chunk> getRenderableChunks()
	{
		Vector<Chunk> renderable = new Vector<Chunk>();
		
		for(int i=0;i<chunkList.size();i++)
		{
			if(chunkList.elementAt(i).isWaitingToBeRendered())
			{
				System.err.println("is renderable");
				renderable.add(chunkList.elementAt(i));
				
				chunkList.elementAt(i).setRendered(true);
				chunkList.elementAt(i).setWaitingToBeRendered(false);
				
			}
			
		}
		
		return renderable;
		
	}
	
	public Vector<Integer> getRenderList() {
		return renderList;
	}
	/**
	 * @param renderList the renderList to set
	 */
	public void setRenderList(Vector<Integer> renderList) {
		this.renderList = renderList;
	}
	
	
	public boolean isRenderListEmpty()
	{
		return renderList.isEmpty();
		
	}
	
	public boolean isChunkListEmpty()
	{
		return chunkList.isEmpty();
		
	}
	
	public boolean isloadListEmpty()
	{
		return loadList.isEmpty();
		
	}
	public boolean isVisbileListEmpty()
	{
		return visibleList.isEmpty();
		
	}

	public Vector<Chunk> getChunkList() {
		return chunkList;
	}

	public void setChunkList(Vector<Chunk> chunkList) {
		this.chunkList = chunkList;
	}

	/**
	 * @return the noiseModifer
	 */
	public double[] getNoiseModifer() {
		return noiseModifer;
	}

	/**
	 * @param noiseModifer the noiseModifer to set
	 */
	public void setNoiseModifer(double[] noiseModifer) {
		this.noiseModifer = noiseModifer;
	}

	/**
	 * @return the seed
	 */
	public String getSeed() {
		return seed;
	}

	/**
	 * @param seed the seed to set
	 */
	public void setSeed(String seed) {
		this.seed = seed;
	}
}
